/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { ArkAssignStmt, ArkInstanceInvokeExpr, ArkMethod, Local, Stmt } from "arkanalyzer";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { FileMatcher, MatcherCallback, MatcherTypes, MethodMatcher } from "../../matcher/Matchers";
import { Defects, IssueReport } from "../../model/Defects";
import { Rule } from "../../model/Rule";
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { RuleListUtil } from "../../utils/common/DefectsList";

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoEmptyCharacterClassCheck');

export class NoEmptyCharacterClassCheck implements BaseChecker {
    readonly REG_EXP = 'RegExp';
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    public metaData: BaseMetaData = {
        severity: 2,
        ruleDocPath: "docs/no-empty-character-class-check.md",
        description: "Disallow empty character classes in regular expressions."
    };

    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };

    private methodMatcher: MethodMatcher = {
        matcherType: MatcherTypes.METHOD,
        file: [this.fileMatcher]
    };

    public registerMatchers(): MatcherCallback[] {
        const methodMatcherCb: MatcherCallback = {
            matcher: this.methodMatcher,
            callback: this.check,
        };
        return [methodMatcherCb];
    };

    public check = (target: ArkMethod) => {
        const stmts = target.getBody()?.getCfg().getStmts() ?? [];
        for (const stmt of stmts) {
            if (!(stmt instanceof ArkAssignStmt)) {
                continue;
            }
            let leftOp = stmt.getLeftOp();
            if (!(leftOp instanceof Local)) {
                continue;
            };
            let rightOp = stmt.getRightOp();
            if (rightOp instanceof ArkInstanceInvokeExpr) {
                let rightArgs = rightOp.getArgs()
                for (const rightArg of rightArgs) {
                    let rightArgTyp = rightArg.getType().getTypeString();
                    if (rightArgTyp?.includes(this.REG_EXP)) {
                        this.checkCode(stmt);
                    }
                }
            };
            let leftType = leftOp.getType().getTypeString();
            if (leftType.includes(this.REG_EXP)) {
                this.checkCode(stmt);
            } else {
                let leftUsedStmts = leftOp.getUsedStmts();
                for (const leftUsedStmt of leftUsedStmts) {
                    let leftArgs = leftUsedStmt.getInvokeExpr()?.getArgs() ?? [];
                    for (const leftArg of leftArgs) {
                        let leftArgType = leftArg.getType().getTypeString();
                        if (leftArgType?.includes(this.REG_EXP)) {
                            this.checkCode(stmt);
                        }
                    }
                }
            }
        }
    };

    private checkCode(stmt: Stmt,) {
        const originText = stmt.getOriginalText() ?? '';
        const regex = /(?<!\\)\[(?<!\\)\]/;
        const match = originText.match(regex);
        if (match) {
            const message = "Empty class"
            this.addIssueReport(stmt, match[0], message);
        }
    }
    private addIssueReport(stmt: Stmt, leftOpName: string, message?: string) {
        let currentDescription = message ? message : this.metaData.description;
        const severity = this.rule.alert ?? this.metaData.severity;
        const warnInfo = this.getLineAndColumn(stmt, leftOpName);
        let defect = new Defects(warnInfo.line, warnInfo.startCol, warnInfo.endCol, this.metaData.description, severity, this.rule.ruleId,
            warnInfo.filePath, this.metaData.ruleDocPath, true, false, false);
        this.issues.push(new IssueReport(defect, undefined));
        RuleListUtil.push(defect);
    }

    private getLineAndColumn(stmt: Stmt, leftOpName: string) {
        const originPosition = stmt.getOriginPositionInfo();
        const line = originPosition.getLineNo();
        const arkFile = stmt.getCfg()?.getDeclaringMethod().getDeclaringArkFile();
        if (arkFile) {
            const originText = stmt.getOriginalText() ?? '';
            let startCol = originPosition.getColNo();
            let result = originText.includes(leftOpName);
            if (result) {
                const pos = originText.indexOf(leftOpName);
                if (pos !== -1) {
                    startCol += pos;
                    const endCol = startCol + originText.length - 1;
                    const originPath = stmt.getCfg()?.getDeclaringMethod().getDeclaringArkFile().getFilePath();
                    return { line, startCol, endCol, filePath: originPath };
                }
            };
        } else {
            logger.debug('originStmt or arkFile is null');
        }
        return { line: -1, startCol: -1, endCol: 0, filePath: '' };
    }
}
